﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.Practices.CompositeUI;
using Kent.Boogaart.HelperTrinity;
using WPFCABShell.Shell.Interface.Services.Command;
using WPFCABShell.Shell.Interface.Services.Settings;
using Microsoft.Practices.ObjectBuilder;

namespace Shortcuts.Services
{
	public sealed class ShortcutsService : IShortcutsService, IBuilderAware
	{
		private readonly ObservableCollection<IShortcutGroup> _groups;
		private readonly ISettings _settings;
		private const string _userSettingsName = "Shortcuts.Services.ShortcutsService";
		private const string _userSettingsKey = "Shortcuts.Services.ShortcutsService.ShortcutGroups";

		public ICollection<IShortcutGroup> Groups
		{
			get
			{
				return _groups;
			}
		}

		public ShortcutsService([ServiceDependency(Required = true)] ISettingsService settingsService,
			[ServiceDependency(Required = true)] ICommandService commandService)
		{
			_settings = settingsService.GetSettings(_userSettingsName);
			_settings.AddSerializationSurrogate(typeof(ObservableCollection<IShortcutGroup>), new ObservableCollectionSerializationSurrogate<IShortcutGroup>());
			_settings.AddSerializationSurrogate(typeof(ObservableCollection<IShortcut>), new ObservableCollectionSerializationSurrogate<IShortcut>());

			if (_settings.Contains(_userSettingsKey))
			{
				_groups = _settings.Get(_userSettingsKey) as ObservableCollection<IShortcutGroup>;

				if (_groups != null)
				{
					//commands aren't serialized (just their IDs are) - therefore, we let the commands resolve here after deserializing
					ResolveCommands(commandService);
				}
			}

			if (_groups == null)
			{
				_groups = new ObservableCollection<IShortcutGroup>();
			}

			_settings.Add(_userSettingsKey, _groups);
		}

		private void ResolveCommands(ICommandService commandService)
		{
			foreach (ShortcutGroup shortcutGroup in _groups)
			{
				foreach (Shortcut shortcut in shortcutGroup.Shortcuts)
				{
					shortcut.ResolveCommand(commandService);
				}
			}
		}

		public IShortcutGroup AddGroup(string name)
		{
			ShortcutGroup shortcutGroup = new ShortcutGroup();
			shortcutGroup.Name = name;
			_groups.Add(shortcutGroup);
			return shortcutGroup;
		}

		void IBuilderAware.OnBuiltUp(string id)
		{
		}

		void IBuilderAware.OnTearingDown()
		{
			_settings.Save();
		}

		#region Supporting Types

		private sealed class ObservableCollectionSerializationSurrogate<T> : ISerializationSurrogate
		{
			private const string _itemsKey = "items";

			public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
			{
				Debug.Assert(obj is ObservableCollection<T>);
				ObservableCollection<T> observableCollection = obj as ObservableCollection<T>;
				T[] items = new T[observableCollection.Count];
				observableCollection.CopyTo(items, 0);
				info.AddValue(_itemsKey, items);
			}

			public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
			{
				T[] items = info.GetValue(_itemsKey, typeof(T[])) as T[];
				return new ObservableCollection<T>(new List<T>(items));
			}
		}

		[Serializable]
		private sealed class ShortcutGroup : IShortcutGroup, INotifyPropertyChanged
		{
			private string _name;
			private bool _isExpanded;
			private readonly ObservableCollection<IShortcut> _shortcuts;

			public string Name
			{
				get
				{
					return _name;
				}
				set
				{
					if (_name != value)
					{
						_name = value;
						OnPropertyChanged("Name");
					}
				}
			}

			public bool IsExpanded
			{
				get
				{
					return _isExpanded;
				}
				set
				{
					if (_isExpanded != value)
					{
						_isExpanded = value;
						OnPropertyChanged("IsExpanded");
					}
				}
			}

			public ICollection<IShortcut> Shortcuts
			{
				get
				{
					return _shortcuts;
				}
			}

			[field: NonSerialized]
			public event PropertyChangedEventHandler PropertyChanged;

			public ShortcutGroup()
			{
				_shortcuts = new ObservableCollection<IShortcut>();
				_isExpanded = true;
			}

			public IShortcut AddShortCut(ICommand command)
			{
				Shortcut shortcut = new Shortcut(this, command);
				_shortcuts.Add(shortcut);
				return shortcut;
			}

			private void OnPropertyChanged(string propertyName)
			{
				EventHelper.Raise(PropertyChanged, this, new PropertyChangedEventArgs(propertyName));
			}
		}

		[Serializable]
		private sealed class Shortcut : IShortcut, INotifyPropertyChanged
		{
			private readonly IShortcutGroup _group;
			private readonly string _commandId;
			[NonSerialized]
			private ICommand _command;

			public IShortcutGroup Group
			{
				get
				{
					return _group;
				}
			}

			public ICommand Command
			{
				get
				{
					return _command;
				}
				private set
				{
					if (_command != value)
					{
						_command = value;
						OnPropertyChanged("Command");
					}
				}
			}

			[field: NonSerialized]
			public event PropertyChangedEventHandler PropertyChanged;

			public Shortcut(IShortcutGroup group, ICommand command)
			{
				Debug.Assert(group != null);
				Debug.Assert(command != null);
				_group = group;
				_commandId = command.Id;
				_command = command;
			}

			public void ResolveCommand(ICommandService commandService)
			{
				Debug.Assert(commandService != null);
				Debug.Assert(_commandId != null);
				Debug.Assert(_command == null);

				foreach (ICommand command in commandService.RegisteredCommands)
				{
					if (string.Equals(_commandId, command.Id, StringComparison.Ordinal))
					{
						Command = command;
						return;
					}
				}

				//command not found - attach in case the command is registered later
				commandService.CommandRegistered += commandService_CommandRegistered;
			}

			private void OnPropertyChanged(string propertyName)
			{
				EventHelper.Raise(PropertyChanged, this, new PropertyChangedEventArgs(propertyName));
			}

			private void commandService_CommandRegistered(object sender, CommandEventArgs e)
			{
				Debug.Assert(sender is ICommandService);

				if (string.Equals(_commandId, e.Command.Id, StringComparison.Ordinal))
				{
					Command = e.Command;
					(sender as ICommandService).CommandRegistered -= commandService_CommandRegistered;
				}
			}
		}

		#endregion
	}
}
